﻿using CocoaAni.Net.WebSockets.Server.DefineInfos;
using CocoaAni.Net.WebSockets.Server.Enums;
using Newtonsoft.Json;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using CocoaAni.Net.WebSockets.Frames;
using CocoaAni.Net.WebSockets.Messages;
using CocoaAni.Net.WebSockets.Server.ArgAdapters.Base;
using CocoaAni.Net.WebSockets.Server.ArgAdapters;
using CocoaAni.Net.WebSockets.Server.Attributes;
using CocoaAni.Net.WebSockets.Server.Configs;
using CocoaAni.Net.WebSockets.Server.Controllers;
using CocoaAni.Net.WebSockets.Server.Exceptions;
using CocoaAni.Net.WebSockets.Server.SendAdapters;
using System.Threading;
using CocoaAni.Net.WebSockets.Enums;

namespace CocoaAni.Net.WebSockets.Server;

public interface IWebSocketServiceDispatcher
{
    public int ClientIdCounter { get; }
    public WebSocketServiceConfig Config { get; }
    public IReadOnlyDictionary<string, ControllerInfo> ControllerInfos { get; }
    public IReadOnlyDictionary<int, WebSocketContext> Contexts { get; }
    public MessageHandlerArgsBuilder ArgsBuilder { get; }
    public IReadOnlyDictionary<Type, IMessageSendAdapter> MessageSendAdapters { get; }
    public void StartDispatch(WebSocket socket, CancellationToken cancellationToken = default);
    public void SendMessage(WebSocketContext context,object message,CancellationToken cancellationToken=default);
    public void BroadcastMessage(object message,ControllerInfo controller,WebSocketContext? excluded=null,CancellationToken cancellationToken=default);
    public void BroadcastMessage(object message, IEnumerable<WebSocketContext> contexts, WebSocketContext? excluded = null, CancellationToken cancellationToken = default);
    public void BroadcastMessage(object message, WebSocketContext? excluded = null, CancellationToken cancellationToken = default);
}

public class WebSocketServiceDispatcher:IWebSocketServiceDispatcher
{

    public WebSocketServiceDispatcher(
        WebSocketServiceConfig config,
        MessageHandlerArgsBuilder argsBuilder, 
        Dictionary<Type, IMessageSendAdapter> messageSendAdapters, 
        Dictionary<string, ControllerInfo> controllerInfos)
    {
        Config=config;
        Contexts = new Dictionary<int, WebSocketContext>();
        ArgsBuilder=argsBuilder;
        MessageSendAdapters=messageSendAdapters;
        ControllerInfos = controllerInfos;
    }

    public int ClientIdCounter { get; set; }
    public WebSocketServiceConfig Config { get; }
    public MessageHandlerArgsBuilder ArgsBuilder { get; }
    public Dictionary<int, WebSocketContext> Contexts { get; }
    public Dictionary<Type, IMessageSendAdapter> MessageSendAdapters { get; }
    public Dictionary<string,ControllerInfo> ControllerInfos { get; }

    IReadOnlyDictionary<string, ControllerInfo> IWebSocketServiceDispatcher.ControllerInfos => ControllerInfos;
    IReadOnlyDictionary<int, WebSocketContext> IWebSocketServiceDispatcher.Contexts => Contexts;
    IReadOnlyDictionary<Type, IMessageSendAdapter> IWebSocketServiceDispatcher.MessageSendAdapters => MessageSendAdapters;

    public void StartDispatch(WebSocket socket, CancellationToken cancellationToken = default)
    {
        var id = 0;
        lock (this)
        {
            id = ClientIdCounter++;
        }
        _ = DispatchInitAsync(id,socket, cancellationToken);
    }

    protected async Task DispatchInitAsync(int id,WebSocket socket, CancellationToken cancellationToken = default)
    {
        try
        {
            var context = new WebSocketContext(id, socket, this);
            //初始化 进行握手 设置握手处理器
            await socket.InitForServerSideAsync((req, resp) =>
            {
                context.HandShakeRequest = req;
                if (!ControllerInfos.ContainsKey(req.Url))
                {
                    throw new WebSocketServiceException($"Controller {req.Url} Not Found!");
                }
                context.ControllerInfo = ControllerInfos[req.Url];
                context.ControllerInfo.Instance.OnConnecting(context);
                context.ControllerInfo.Instance.OnHandShake(req, resp);
                return Task.CompletedTask;
            }, null, cancellationToken);
            //检查是否握手成功
            if (!socket.IsHandShake)
            {
                socket.BaseStream.Close();
                socket.Close();
                return;
            }
            Contexts[id] = context;
            context.DispatcherTask = InternalDispatchAsync(context, cancellationToken);
        }
        catch (Exception e)
        {
            var msg = $"Dispatch Init Error: {e.Message}";
            await Console.Error.WriteLineAsync(msg);
            throw new WebSocketServiceException($"Dispatch Init Error: {e.Message}", e);
        }
        
    }

    private async Task InternalDispatchAsync(WebSocketContext context, CancellationToken cancellationToken)
    {
        context.MessageReceiveBuffer = new ReceiveBuffer(Config.ClientReceiveBufferSize);
        context.MessageHandlerInfo=context.ControllerInfo.HandlerInfos[MessageHandlerAttribute.Default];
        var messageEnumerable = MessageReceiveDispatchAsync(context, cancellationToken);
        // ReSharper disable once UnusedVariable
        try
        {
            object? invokeResult = null;
            invokeResult = context.ControllerInfo.Instance.OnOpen(context);
            await TrySendMessage(context, invokeResult, cancellationToken);
            await foreach (var message in messageEnumerable.WithCancellation(cancellationToken))
            {
                
                try
                { 
                    switch (message.Opcode)
                    {
                        case WSOpcode.Ping:
                            invokeResult = context.ControllerInfo.Instance.OnPing(context);
                            break;
                        case WSOpcode.Text:
                        case WSOpcode.Binary:
                            var handleInfo = context.MessageHandlerInfo;
                            var invokeArgs = ArgsBuilder.BuildArgs(context);
                            invokeResult = handleInfo.Handler.Invoke(context.ControllerInfo.Instance, invokeArgs);
                            break;
                        case WSOpcode.Close:
                            context.ControllerInfo.Instance.OnCloseReceived(context);
                            context.Socket.Close();
                            context.ControllerInfo.Instance.OnClosed(context);
                            return;
                        case WSOpcode.Continue:
                        case WSOpcode.Pong:
                        case WSOpcode.Undefined:
                        default:
                            throw new WebSocketContextException(
                                $"Receive Not Support Message,Opcode Is {message.Opcode}");
                    }
                    await TrySendMessage(context, invokeResult, cancellationToken);
                }
                catch (WebSocketMessageArgException e)
                {
                    invokeResult = context.ControllerInfo.Instance.OnMessageArgError(e);
                    await TrySendMessage(context, invokeResult, cancellationToken);
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine(ex);
                    throw;
                }
            }
        }
        catch (Exception e)
        {
            context.ControllerInfo.Instance.OnClosed(context);
            Console.Error.WriteLine(e);
            throw;
        }
        
    }

    public async Task TrySendMessage(WebSocketContext context, object? target, CancellationToken cancellationToken = default)
    {
        if (target is Task)
        {
            dynamic dynTask = target;
            target = await dynTask;
        }
        if(target==null)return;
        SendMessage(context,target,cancellationToken);
    }

    public void SendMessage(WebSocketContext context,object target,CancellationToken cancellationToken=default)
    {
        lock (context.SendQueue)
        {
            context.SendQueue.Enqueue(target);
            if (!context.IsSending)
            {
                context.IsSending = true;
                _= MessageSendDispatchAsync(context,cancellationToken);
            }
        }
    }
    private static async IAsyncEnumerable<Message> MessageReceiveDispatchAsync(WebSocketContext context, 
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        var socket = context.Socket;
        while (true)
        {
            context.Message = await socket.ReceiveAsync(context.MessageReceiveBuffer, cancellationToken);
            yield return context.Message;
            context.MessageReceiveBuffer.Reset();
        }
    }


    public async Task MessageSendDispatchAsync(WebSocketContext context,CancellationToken cancellationToken=default)
    {
        while (true)
        {
            object target;
            lock (context.SendQueue)
            {
                if (context.SendQueue.Count == 0)
                {
                    context.IsSending = false;
                    return;
                }
                target = context.SendQueue.Dequeue();
            }
            var targetType = target.GetType();
            if (MessageSendAdapters.ContainsKey(targetType))
            {
                await MessageSendAdapters[targetType].SendAsync(context, target, cancellationToken);
            }
            else
            {
                try
                {
                    var serializeObject = JsonConvert.SerializeObject(target);
                    await context.Socket.SendTextAsync(serializeObject,cancellationToken);
                }
                catch (Exception e)
                {
                    throw new WebSocketServiceException($"Message Send Target Type Not Support! Type={targetType}",e);
                }
                
                
                
            }
            
        }

        
    }

    public void BroadcastMessage(object message, ControllerInfo controller, WebSocketContext? excluded = null,CancellationToken cancellationToken=default)
    {
        var targets = Contexts
            .Where(t => t.Value.ControllerInfo == controller)
            .Select(t => t.Value);
        foreach (var webSocketContext in targets)
        {
            if (webSocketContext == excluded) continue;
            SendMessage(webSocketContext,message,cancellationToken);
        }
    }


    public void BroadcastMessage(object message, IEnumerable<WebSocketContext> contexts, WebSocketContext? excluded = null,CancellationToken cancellationToken=default)
    {
        foreach (var webSocketContext in contexts)
        {
            if(webSocketContext==excluded)continue;
            SendMessage(webSocketContext,message,cancellationToken);
        }
    }

    public void BroadcastMessage(object message, WebSocketContext? excluded = null, CancellationToken cancellationToken = default)
    {
        foreach (var webSocketContext in Contexts.Values.Where(webSocketContext => webSocketContext != excluded))
        {
            SendMessage(webSocketContext, message, cancellationToken);
        }
    }
}

//public class WebSocketServiceDispatcher
//{
//    public JsonObjectArgAdapter JsonObjectArgAdapter { get; set; }
//    public JsonObjectPropertiesArgAdapter JsonPropertiesArgAdapter { get; set; }
//    public MessageTextArgAdapter MessageTextArgAdapter { get; set; }
//    public BinaryArgAdapter BinaryArgAdapter { get; set; }
//    public Dictionary<Type, IMessageArgAdapter> MessageArgAdapters { get; set; }
//    public WebSocketService Service { get; set; }

//    public WebSocketServiceDispatcher(WebSocketService service)
//    {
//        Service = service;
//        JsonObjectArgAdapter = service.Config.JsonObjectArgAdapter;
//        JsonPropertiesArgAdapter = service.Config.JsonPropertiesArgAdapter;
//        MessageTextArgAdapter = service.Config.MessageTextArgAdapter;
//        BinaryArgAdapter = service.Config.BinaryArgAdapter;
//        MessageArgAdapters = service.MessageArgAdapters;
//    }

//    public Task StartDispatchAsync(Socket socket, CancellationToken cancellationToken = default)
//    {
//        var clientId = 0;
//        lock (this)
//        {
//            clientId = Service.ClientIdCounter++;
//        }
//        var context = new WebSocketContext(clientId, socket, Service);
//        Service.Contexts[clientId] = context;
//        context.ReceiveTaskWorker = StartReceiveTaskAsync(context, cancellationToken);
//        return Task.CompletedTask;
//    }

//    protected internal async Task StartReceiveTaskAsync(WebSocketContext context, CancellationToken cancellationToken = default)
//    {
//        try
//        {
//            var socket = context.Socket;
//            await context.InitAsync(cancellationToken);
//            while (true)
//            {
//                context.JsonObject = null;
//                context.IsJsonObject = true;
//                context.Message = await socket.ReceiveAsync(10240, cancellationToken);
//                var messageHandlerInfo = context.ControllerInfo.HandlerInfos[context.HandlerName];
//                object?[] parameters;
//                try
//                {
//                    parameters = ParseParameters(context, messageHandlerInfo);
//                }
//                catch (Exception e)
//                {
//                    Console.WriteLine(e);
//                    Send(context, e.Message, cancellationToken);
//                    continue;
//                }
//                object? resultValue;
//                dynamic invokeResult = resultValue = messageHandlerInfo.Handler.Invoke(context.ControllerInfo.Instance, parameters)!;
//                if (invokeResult == null)
//                    continue;
//                if (invokeResult is Task)
//                    resultValue = await invokeResult;

//                if (resultValue == null)
//                    continue;
//                Send(context, resultValue, cancellationToken);
//            }
//        }
//        catch (Exception e)
//        {
//            context.ReceiveTaskError = e;
//            Console.WriteLine(e);
//        }
//    }

//    protected internal void Send(WebSocketContext context, object target, CancellationToken cancellationToken = default)
//    {
//        lock (context.SendQueue)
//        {
//            context.SendQueue.Enqueue(target);
//            if (context.IsSending == Task.CompletedTask)
//            {
//                context.IsSending = StartSendTaskAsync(context, cancellationToken);
//            }
//        }
//    }

//    protected internal async Task StartSendTaskAsync(WebSocketContext context, CancellationToken cancellationToken = default)
//    {
//        try
//        {
//            while (true)
//            {
//                object target;
//                lock (context.SendQueue)
//                {
//                    if (context.SendQueue.Count == 0)
//                    {
//                        context.IsSending = Task.CompletedTask;
//                        return;
//                    }
//                    target = context.SendQueue.Dequeue();
//                }
//                var socket = context.Socket;
//                if (target is string str)
//                {
//                    await socket.SendTextAsync(str, cancellationToken);
//                    continue;
//                }
//                var serializeObject = JsonConvert.SerializeObject(target);
//                if (serializeObject == null)
//                {
//                    continue;
//                }
//                await socket.SendTextAsync(serializeObject, cancellationToken);
//            }
//        }
//        catch (Exception e)
//        {
//            context.SendTaskError = e;
//            Console.WriteLine(e);
//            throw;
//        }
//    }

//    protected object?[] ParseParameters(WebSocketContext context, HandlerDefineInfo handlerInfo)
//    {
//        var parameters = new object?[handlerInfo.ArgInfos.Length];
//        for (var i = 0; i < handlerInfo.ArgInfos.Length; i++)
//        {
//            var defInfo = handlerInfo.ArgInfos[i];
//            if (defInfo.Adapter == null)
//            {
//                if (MessageArgAdapters.ContainsKey(defInfo.Type))
//                {
//                    defInfo.Adapter = MessageArgAdapters[defInfo.Type];
//                }
//                else
//                {
//                    defInfo.Adapter = defInfo.Category switch
//                    {
//                        ArgCategory.JsonObject => Service.Config.JsonObjectArgAdapter,
//                        ArgCategory.JsonProperties => Service.Config.JsonPropertiesArgAdapter,
//                        ArgCategory.MessageText => Service.Config.MessageTextArgAdapter,
//                        ArgCategory.MessageBinary => Service.Config.BinaryArgAdapter,
//                        _ => throw new NotSupportedException(defInfo.Category.ToString())
//                    };
//                }
//            }
//            parameters[i] = defInfo.Adapter.GetTargetParameter(context, defInfo);
//        }
//        return parameters;
//    }
//}